None
Из «Бета-Банка» стали уходить клиенты. Каждый месяц. Немного, но заметно. Банковские маркетологи посчитали: сохранять текущих клиентов дешевле, чем привлекать новых.
Цели:
Признаки:
Целевой признак
Источник данных: https://www.kaggle.com/barelydedicated/bank-customer-churn-modeling
import pandas as pd
# для модели проверки адекватности
# from numpy.random import choice
# для графиков
import matplotlib.pyplot as plt
# метрика время исполнения
import time
# для разделения данных на выборки
from sklearn.model_selection import train_test_split
# метрика для моделей- accuracy
# from sklearn.metrics import accuracy_score
# метрика для моделей- F1-мера
from sklearn.metrics import f1_score
# метрика для моделей AUC-ROC
from sklearn.metrics import roc_auc_score
# Классификатор Дерево решений
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import plot_tree
# Классификатор Случайный Лес
from sklearn.ensemble import RandomForestClassifier
# Классификатор Логистическая регрессия
from sklearn.linear_model import LogisticRegression
# Классификатор для проверки адекватности
# from sklearn.dummy import DummyClassifier
# для перевода категорийных данных от строк к числам
from sklearn.preprocessing import OneHotEncoder
# для маштабирования признаков
from sklearn.preprocessing import StandardScaler
# для перемешивания выборок
from sklearn.utils import shuffle
# для изображения PR-кривой
from sklearn.metrics import precision_recall_curve
# для изображения ROC-кривой
from sklearn.metrics import roc_curve
# метрики точности и полноты
from sklearn.metrics import precision_score, recall_score
# для скрытия ошибок
import warnings
import numpy as np
# для скрытия ошибок
warnings.filterwarnings("ignore")
# pd.options.mode.chained_assignment = None
# для увеличения окна вывода
pd.options.display.max_rows = 300
Загрузим данные из файла.
# версия для ревьюера
data = pd.read_csv('/datasets/Churn.csv')
# моя локальная версия
# data = pd.read_csv('Churn.csv')
data.head()
| RowNumber | CustomerId | Surname | CreditScore | Geography | Gender | Age | Tenure | Balance | NumOfProducts | HasCrCard | IsActiveMember | EstimatedSalary | Exited | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 15634602 | Hargrave | 619 | France | Female | 42 | 2.0 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 |
| 1 | 2 | 15647311 | Hill | 608 | Spain | Female | 41 | 1.0 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 |
| 2 | 3 | 15619304 | Onio | 502 | France | Female | 42 | 8.0 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 |
| 3 | 4 | 15701354 | Boni | 699 | France | Female | 39 | 1.0 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 |
| 4 | 5 | 15737888 | Mitchell | 850 | Spain | Female | 43 | 2.0 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 |
Переименуем колонки
display(data.columns)
data.columns = ['row_number', 'customer_id', 'surname', 'credit_score', 'geography',
'gender', 'age', 'tenure', 'balance', 'num_of_products', 'has_cr_card',
'is_active_member', 'estimated_salary', 'exited']
data.columns
Index(['RowNumber', 'CustomerId', 'Surname', 'CreditScore', 'Geography',
'Gender', 'Age', 'Tenure', 'Balance', 'NumOfProducts', 'HasCrCard',
'IsActiveMember', 'EstimatedSalary', 'Exited'],
dtype='object')
Index(['row_number', 'customer_id', 'surname', 'credit_score', 'geography',
'gender', 'age', 'tenure', 'balance', 'num_of_products', 'has_cr_card',
'is_active_member', 'estimated_salary', 'exited'],
dtype='object')
Обновленные Признаки:
Обновленный Целевой Признак
Функция для изучения данных
# Напишем фунцию для получения информации о данных
def data_frame_info (data_frame):
display(data_frame.head())
display('-'* 25)
list_c = data_frame.columns
display('Количество явных дубликатов:', data_frame.duplicated().sum())
display('-'* 25)
display(data_frame.info())
display('-'* 25)
display(data_frame.hist(figsize=(30, 20)))
plt.title('Гистограммы для данных в столбцах')
plt.show()
display('-'* 25)
display(data_frame.corr())
display('-'* 25)
pd.plotting.scatter_matrix(data_frame, figsize=(30, 20))
plt.title('Попарные графики распределения')
plt.show()
for col_l in list_c:
display('-'* 25)
display('Информация для колоки', col_l)
display(data_frame[col_l].describe())
display('-'* 25)
display('Уникальные значения:')
display(col_l, data_frame[col_l].sort_values().unique())
display('-'* 25)
display('Колличество каждого уникального значения:')
display(data_frame[col_l].value_counts(sort=True, ascending=False))
display('-'* 25)
display('Пропуски:')
display(col_l,': кол-во NaN',data_frame[col_l].isna().sum(),
', процент NaN', round(data_frame[col_l].isna().sum()/len(data_frame)*100, 2),'%')
data_frame_info(data)
| row_number | customer_id | surname | credit_score | geography | gender | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | exited | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 15634602 | Hargrave | 619 | France | Female | 42 | 2.0 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 |
| 1 | 2 | 15647311 | Hill | 608 | Spain | Female | 41 | 1.0 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 |
| 2 | 3 | 15619304 | Onio | 502 | France | Female | 42 | 8.0 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 |
| 3 | 4 | 15701354 | Boni | 699 | France | Female | 39 | 1.0 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 |
| 4 | 5 | 15737888 | Mitchell | 850 | Spain | Female | 43 | 2.0 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 |
'-------------------------'
'Количество явных дубликатов:'
0
'-------------------------'
<class 'pandas.core.frame.DataFrame'> RangeIndex: 10000 entries, 0 to 9999 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 row_number 10000 non-null int64 1 customer_id 10000 non-null int64 2 surname 10000 non-null object 3 credit_score 10000 non-null int64 4 geography 10000 non-null object 5 gender 10000 non-null object 6 age 10000 non-null int64 7 tenure 9091 non-null float64 8 balance 10000 non-null float64 9 num_of_products 10000 non-null int64 10 has_cr_card 10000 non-null int64 11 is_active_member 10000 non-null int64 12 estimated_salary 10000 non-null float64 13 exited 10000 non-null int64 dtypes: float64(3), int64(8), object(3) memory usage: 1.1+ MB
None
'-------------------------'
array([[<AxesSubplot:title={'center':'row_number'}>,
<AxesSubplot:title={'center':'customer_id'}>,
<AxesSubplot:title={'center':'credit_score'}>],
[<AxesSubplot:title={'center':'age'}>,
<AxesSubplot:title={'center':'tenure'}>,
<AxesSubplot:title={'center':'balance'}>],
[<AxesSubplot:title={'center':'num_of_products'}>,
<AxesSubplot:title={'center':'has_cr_card'}>,
<AxesSubplot:title={'center':'is_active_member'}>],
[<AxesSubplot:title={'center':'estimated_salary'}>,
<AxesSubplot:title={'center':'exited'}>, <AxesSubplot:>]],
dtype=object)
'-------------------------'
| row_number | customer_id | credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | exited | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| row_number | 1.000000 | 0.004202 | 0.005840 | 0.000783 | -0.007322 | -0.009067 | 0.007246 | 0.000599 | 0.012044 | -0.005988 | -0.016571 |
| customer_id | 0.004202 | 1.000000 | 0.005308 | 0.009497 | -0.021418 | -0.012419 | 0.016972 | -0.014025 | 0.001665 | 0.015271 | -0.006248 |
| credit_score | 0.005840 | 0.005308 | 1.000000 | -0.003965 | -0.000062 | 0.006268 | 0.012238 | -0.005458 | 0.025651 | -0.001384 | -0.027094 |
| age | 0.000783 | 0.009497 | -0.003965 | 1.000000 | -0.013134 | 0.028308 | -0.030680 | -0.011721 | 0.085472 | -0.007201 | 0.285323 |
| tenure | -0.007322 | -0.021418 | -0.000062 | -0.013134 | 1.000000 | -0.007911 | 0.011979 | 0.027232 | -0.032178 | 0.010520 | -0.016761 |
| balance | -0.009067 | -0.012419 | 0.006268 | 0.028308 | -0.007911 | 1.000000 | -0.304180 | -0.014858 | -0.010084 | 0.012797 | 0.118533 |
| num_of_products | 0.007246 | 0.016972 | 0.012238 | -0.030680 | 0.011979 | -0.304180 | 1.000000 | 0.003183 | 0.009612 | 0.014204 | -0.047820 |
| has_cr_card | 0.000599 | -0.014025 | -0.005458 | -0.011721 | 0.027232 | -0.014858 | 0.003183 | 1.000000 | -0.011866 | -0.009933 | -0.007138 |
| is_active_member | 0.012044 | 0.001665 | 0.025651 | 0.085472 | -0.032178 | -0.010084 | 0.009612 | -0.011866 | 1.000000 | -0.011421 | -0.156128 |
| estimated_salary | -0.005988 | 0.015271 | -0.001384 | -0.007201 | 0.010520 | 0.012797 | 0.014204 | -0.009933 | -0.011421 | 1.000000 | 0.012097 |
| exited | -0.016571 | -0.006248 | -0.027094 | 0.285323 | -0.016761 | 0.118533 | -0.047820 | -0.007138 | -0.156128 | 0.012097 | 1.000000 |
'-------------------------'
'-------------------------'
'Информация для колоки'
'row_number'
count 10000.00000 mean 5000.50000 std 2886.89568 min 1.00000 25% 2500.75000 50% 5000.50000 75% 7500.25000 max 10000.00000 Name: row_number, dtype: float64
'-------------------------'
'Уникальные значения:'
'row_number'
array([ 1, 2, 3, ..., 9998, 9999, 10000], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
2049 1
8865 1
6806 1
4759 1
8857 1
..
9526 1
5432 1
7481 1
1338 1
2047 1
Name: row_number, Length: 10000, dtype: int64
'-------------------------'
'Пропуски:'
'row_number'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'customer_id'
count 1.000000e+04 mean 1.569094e+07 std 7.193619e+04 min 1.556570e+07 25% 1.562853e+07 50% 1.569074e+07 75% 1.575323e+07 max 1.581569e+07 Name: customer_id, dtype: float64
'-------------------------'
'Уникальные значения:'
'customer_id'
array([15565701, 15565706, 15565714, ..., 15815656, 15815660, 15815690],
dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
15695872 1
15801062 1
15682268 1
15647453 1
15684319 1
..
15629677 1
15773039 1
15766896 1
15719793 1
15812607 1
Name: customer_id, Length: 10000, dtype: int64
'-------------------------'
'Пропуски:'
'customer_id'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'surname'
count 10000 unique 2932 top Smith freq 32 Name: surname, dtype: object
'-------------------------'
'Уникальные значения:'
'surname'
array(['Abazu', 'Abbie', 'Abbott', ..., 'Zuev', 'Zuyev', 'Zuyeva'],
dtype=object)
'-------------------------'
'Колличество каждого уникального значения:'
Smith 32
Martin 29
Scott 29
Walker 28
Brown 26
..
Fiore 1
Fulks 1
Rutledge 1
Wofford 1
Borchgrevink 1
Name: surname, Length: 2932, dtype: int64
'-------------------------'
'Пропуски:'
'surname'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'credit_score'
count 10000.000000 mean 650.528800 std 96.653299 min 350.000000 25% 584.000000 50% 652.000000 75% 718.000000 max 850.000000 Name: credit_score, dtype: float64
'-------------------------'
'Уникальные значения:'
'credit_score'
array([350, 351, 358, 359, 363, 365, 367, 373, 376, 382, 383, 386, 395,
399, 401, 404, 405, 407, 408, 410, 411, 412, 413, 414, 415, 416,
417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429,
430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442,
443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455,
456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468,
469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481,
482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494,
495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507,
508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520,
521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533,
534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546,
547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559,
560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572,
573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585,
586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598,
599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611,
612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624,
625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637,
638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650,
651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663,
664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676,
677, 678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689,
690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702,
703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715,
716, 717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728,
729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741,
742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754,
755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767,
768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780,
781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793,
794, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806,
807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818, 819,
820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832,
833, 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, 845,
846, 847, 848, 849, 850], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
850 233
678 63
655 54
667 53
705 53
...
412 1
351 1
365 1
373 1
423 1
Name: credit_score, Length: 460, dtype: int64
'-------------------------'
'Пропуски:'
'credit_score'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'geography'
count 10000 unique 3 top France freq 5014 Name: geography, dtype: object
'-------------------------'
'Уникальные значения:'
'geography'
array(['France', 'Germany', 'Spain'], dtype=object)
'-------------------------'
'Колличество каждого уникального значения:'
France 5014 Germany 2509 Spain 2477 Name: geography, dtype: int64
'-------------------------'
'Пропуски:'
'geography'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'gender'
count 10000 unique 2 top Male freq 5457 Name: gender, dtype: object
'-------------------------'
'Уникальные значения:'
'gender'
array(['Female', 'Male'], dtype=object)
'-------------------------'
'Колличество каждого уникального значения:'
Male 5457 Female 4543 Name: gender, dtype: int64
'-------------------------'
'Пропуски:'
'gender'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'age'
count 10000.000000 mean 38.921800 std 10.487806 min 18.000000 25% 32.000000 50% 37.000000 75% 44.000000 max 92.000000 Name: age, dtype: float64
'-------------------------'
'Уникальные значения:'
'age'
array([18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85,
88, 92], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
37 478 38 477 35 474 36 456 34 447 33 442 40 432 39 423 32 418 31 404 41 366 29 348 30 327 42 321 43 297 28 273 44 257 45 229 46 226 27 209 26 200 47 175 48 168 25 154 49 147 50 134 24 132 51 119 52 102 23 99 22 84 54 84 55 82 57 75 53 74 56 70 58 67 59 62 60 62 61 53 21 53 62 52 63 40 20 40 67 37 64 37 66 35 71 27 19 27 18 22 69 22 72 21 68 19 65 18 74 18 70 18 73 13 76 11 77 10 75 9 78 5 79 4 81 4 80 3 84 2 92 2 88 1 82 1 85 1 83 1 Name: age, dtype: int64
'-------------------------'
'Пропуски:'
'age'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'tenure'
count 9091.000000 mean 4.997690 std 2.894723 min 0.000000 25% 2.000000 50% 5.000000 75% 7.000000 max 10.000000 Name: tenure, dtype: float64
'-------------------------'
'Уникальные значения:'
'tenure'
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., nan])
'-------------------------'
'Колличество каждого уникального значения:'
1.0 952 2.0 950 8.0 933 3.0 928 5.0 927 7.0 925 4.0 885 9.0 882 6.0 881 10.0 446 0.0 382 Name: tenure, dtype: int64
'-------------------------'
'Пропуски:'
'tenure'
': кол-во NaN'
909
', процент NaN'
9.09
'%'
'-------------------------'
'Информация для колоки'
'balance'
count 10000.000000 mean 76485.889288 std 62397.405202 min 0.000000 25% 0.000000 50% 97198.540000 75% 127644.240000 max 250898.090000 Name: balance, dtype: float64
'-------------------------'
'Уникальные значения:'
'balance'
array([ 0. , 3768.69, 12459.19, ..., 222267.63, 238387.56,
250898.09])
'-------------------------'
'Колличество каждого уникального значения:'
0.00 3617
105473.74 2
130170.82 2
72594.00 1
139723.90 1
...
130306.49 1
92895.56 1
132005.77 1
166287.85 1
104001.38 1
Name: balance, Length: 6382, dtype: int64
'-------------------------'
'Пропуски:'
'balance'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'num_of_products'
count 10000.000000 mean 1.530200 std 0.581654 min 1.000000 25% 1.000000 50% 1.000000 75% 2.000000 max 4.000000 Name: num_of_products, dtype: float64
'-------------------------'
'Уникальные значения:'
'num_of_products'
array([1, 2, 3, 4], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 5084 2 4590 3 266 4 60 Name: num_of_products, dtype: int64
'-------------------------'
'Пропуски:'
'num_of_products'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'has_cr_card'
count 10000.00000 mean 0.70550 std 0.45584 min 0.00000 25% 0.00000 50% 1.00000 75% 1.00000 max 1.00000 Name: has_cr_card, dtype: float64
'-------------------------'
'Уникальные значения:'
'has_cr_card'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 7055 0 2945 Name: has_cr_card, dtype: int64
'-------------------------'
'Пропуски:'
'has_cr_card'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'is_active_member'
count 10000.000000 mean 0.515100 std 0.499797 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: is_active_member, dtype: float64
'-------------------------'
'Уникальные значения:'
'is_active_member'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 5151 0 4849 Name: is_active_member, dtype: int64
'-------------------------'
'Пропуски:'
'is_active_member'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'estimated_salary'
count 10000.000000 mean 100090.239881 std 57510.492818 min 11.580000 25% 51002.110000 50% 100193.915000 75% 149388.247500 max 199992.480000 Name: estimated_salary, dtype: float64
'-------------------------'
'Уникальные значения:'
'estimated_salary'
array([1.1580000e+01, 9.0070000e+01, 9.1750000e+01, ..., 1.9995333e+05,
1.9997074e+05, 1.9999248e+05])
'-------------------------'
'Колличество каждого уникального значения:'
24924.92 2
109145.20 1
59755.14 1
1557.82 1
117202.19 1
..
37674.47 1
158043.11 1
103792.53 1
182266.01 1
155061.97 1
Name: estimated_salary, Length: 9999, dtype: int64
'-------------------------'
'Пропуски:'
'estimated_salary'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'exited'
count 10000.000000 mean 0.203700 std 0.402769 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1.000000 Name: exited, dtype: float64
'-------------------------'
'Уникальные значения:'
'exited'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
0 7963 1 2037 Name: exited, dtype: int64
'-------------------------'
'Пропуски:'
'exited'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
Выводы:
Посмотрим на данные в tenure еще раз.
data[data['tenure'].isna()].sample(5)
| row_number | customer_id | surname | credit_score | geography | gender | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | exited | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 3100 | 3101 | 15735549 | Lori | 810 | Germany | Male | 35 | NaN | 96814.46 | 2 | 1 | 1 | 120511.03 | 0 |
| 2630 | 2631 | 15600879 | Parsons | 554 | Germany | Female | 36 | NaN | 157780.93 | 2 | 1 | 0 | 6089.13 | 0 |
| 1202 | 1203 | 15724334 | Alekseyeva | 529 | France | Male | 22 | NaN | 0.00 | 1 | 1 | 0 | 151169.83 | 0 |
| 7053 | 7054 | 15815271 | Ritchie | 755 | Germany | Male | 43 | NaN | 165048.50 | 3 | 1 | 0 | 16929.41 | 1 |
| 353 | 354 | 15812007 | Power | 670 | Spain | Male | 25 | NaN | 0.00 | 2 | 1 | 1 | 78358.94 | 0 |
Явных зависимостей не видно.
Посмотрим статиситку.
data['tenure'].describe()
count 9091.000000 mean 4.997690 std 2.894723 min 0.000000 25% 2.000000 50% 5.000000 75% 7.000000 max 10.000000 Name: tenure, dtype: float64
data['tenure'].value_counts()
1.0 952 2.0 950 8.0 933 3.0 928 5.0 927 7.0 925 4.0 885 9.0 882 6.0 881 10.0 446 0.0 382 Name: tenure, dtype: int64
data['tenure'].isna().sum()
909
Выводы:
Пропуски.
data.dropna(inplace=True)
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 9091 entries, 0 to 9998 Data columns (total 14 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 row_number 9091 non-null int64 1 customer_id 9091 non-null int64 2 surname 9091 non-null object 3 credit_score 9091 non-null int64 4 geography 9091 non-null object 5 gender 9091 non-null object 6 age 9091 non-null int64 7 tenure 9091 non-null float64 8 balance 9091 non-null float64 9 num_of_products 9091 non-null int64 10 has_cr_card 9091 non-null int64 11 is_active_member 9091 non-null int64 12 estimated_salary 9091 non-null float64 13 exited 9091 non-null int64 dtypes: float64(3), int64(8), object(3) memory usage: 1.0+ MB
Удаляем не нужные столбцы.
data.drop(columns=['row_number', 'surname', 'customer_id'], inplace=True)
data.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 9091 entries, 0 to 9998 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 credit_score 9091 non-null int64 1 geography 9091 non-null object 2 gender 9091 non-null object 3 age 9091 non-null int64 4 tenure 9091 non-null float64 5 balance 9091 non-null float64 6 num_of_products 9091 non-null int64 7 has_cr_card 9091 non-null int64 8 is_active_member 9091 non-null int64 9 estimated_salary 9091 non-null float64 10 exited 9091 non-null int64 dtypes: float64(3), int64(6), object(2) memory usage: 852.3+ KB
Выводы:
data содержат нужную информацию.Преобразуем категорийные данные в строках в столбцах geography, gender по технологии one-hot-encoding: столбцы-признаки- категории с 1, если в исходном столбце была эта категория. При этом мы удалим первый столбец, так как его можно восстановить по остальным столбцам этой категории.
data_ohe = pd.get_dummies(data, columns=['geography', 'gender'], drop_first=True, dtype='int')
data_ohe.head()
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | exited | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 619 | 42 | 2.0 | 0.00 | 1 | 1 | 1 | 101348.88 | 1 | 0 | 0 | 0 |
| 1 | 608 | 41 | 1.0 | 83807.86 | 1 | 0 | 1 | 112542.58 | 0 | 0 | 1 | 0 |
| 2 | 502 | 42 | 8.0 | 159660.80 | 3 | 1 | 0 | 113931.57 | 1 | 0 | 0 | 0 |
| 3 | 699 | 39 | 1.0 | 0.00 | 2 | 0 | 0 | 93826.63 | 0 | 0 | 0 | 0 |
| 4 | 850 | 43 | 2.0 | 125510.82 | 1 | 1 | 1 | 79084.10 | 0 | 0 | 1 | 0 |
data_ohe.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 9091 entries, 0 to 9998 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 credit_score 9091 non-null int64 1 age 9091 non-null int64 2 tenure 9091 non-null float64 3 balance 9091 non-null float64 4 num_of_products 9091 non-null int64 5 has_cr_card 9091 non-null int64 6 is_active_member 9091 non-null int64 7 estimated_salary 9091 non-null float64 8 exited 9091 non-null int64 9 geography_Germany 9091 non-null int32 10 geography_Spain 9091 non-null int32 11 gender_Male 9091 non-null int32 dtypes: float64(3), int32(3), int64(6) memory usage: 816.8 KB
Выводы:
Мы будем решать нашу задачу как задачу Обучение с учителем, тоесть мы будем обучать наши модели с известным целевым признаком, и Классификации, тоесть модель после рассморения объекта будет предсказывать не число, а категорию, в нашем случае, если exited = 1, то клиент может уйти, если exited = 0, то этот клиент скорее всего не в зоне риски ухода из банка.
Для наших целей выделим целевой признак exited в target, а остальные обучающие признаки в featurese.
target = data_ohe['exited']
features = data_ohe.drop(columns='exited')
В этом проекте для вычеслений и выборок будет использоваться механизм генератора псевдослучайных значений. Для нашего проекта мы выбрали этот параметр random_state равным 54321 для всего проекта. Настройка этого значения в пп Настроки.
rs = 54321
Замечания: во время выполнения было обнаружено, что модели связанные с деревьями игнорировали настроки, поэтому было принято решение перестраховаться параметром random_state=rs.
Так как у нас нет тестовой выборки отдельно, то разделим наши данные на 3 пары выборок в следующих пропорциях:
features_train, target_trainfeatures_valid, target_validfeatures_test, target_testМы будем применить тренировочную пару для обучения моделей.
Проверочную пару для выбора гиперпараметров моделей внетри их классов:
Тестовую пару для финального выбора модели среди победителей внутриклассового соревнования.
# 3-1-1 = 60 - 40/2 - 40/2 = 60-20-20
features_train, features_valid, target_train, target_valid = train_test_split(
features, target, test_size=0.40, stratify=target, random_state=rs
)
features_valid, features_test, target_valid, target_test = train_test_split(
features_valid, target_valid, test_size=0.50, stratify=target_valid, random_state=rs
)
Посмотрим на наши выборки.
data_frame_info(features_train)
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 4736 | 638 | 32 | 0.0 | 0.00 | 2 | 1 | 0 | 160129.99 | 0 | 0 | 0 |
| 873 | 561 | 28 | 7.0 | 0.00 | 2 | 1 | 0 | 7797.01 | 0 | 0 | 1 |
| 2093 | 555 | 55 | 4.0 | 146798.81 | 1 | 1 | 1 | 74149.77 | 0 | 0 | 1 |
| 6859 | 733 | 38 | 3.0 | 157658.36 | 1 | 0 | 0 | 19658.43 | 0 | 0 | 0 |
| 7624 | 728 | 69 | 1.0 | 0.00 | 2 | 1 | 1 | 131804.86 | 0 | 0 | 0 |
'-------------------------'
'Количество явных дубликатов:'
0
'-------------------------'
<class 'pandas.core.frame.DataFrame'> Int64Index: 5454 entries, 4736 to 1431 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 credit_score 5454 non-null int64 1 age 5454 non-null int64 2 tenure 5454 non-null float64 3 balance 5454 non-null float64 4 num_of_products 5454 non-null int64 5 has_cr_card 5454 non-null int64 6 is_active_member 5454 non-null int64 7 estimated_salary 5454 non-null float64 8 geography_Germany 5454 non-null int32 9 geography_Spain 5454 non-null int32 10 gender_Male 5454 non-null int32 dtypes: float64(3), int32(3), int64(5) memory usage: 447.4 KB
None
'-------------------------'
array([[<AxesSubplot:title={'center':'credit_score'}>,
<AxesSubplot:title={'center':'age'}>,
<AxesSubplot:title={'center':'tenure'}>],
[<AxesSubplot:title={'center':'balance'}>,
<AxesSubplot:title={'center':'num_of_products'}>,
<AxesSubplot:title={'center':'has_cr_card'}>],
[<AxesSubplot:title={'center':'is_active_member'}>,
<AxesSubplot:title={'center':'estimated_salary'}>,
<AxesSubplot:title={'center':'geography_Germany'}>],
[<AxesSubplot:title={'center':'geography_Spain'}>,
<AxesSubplot:title={'center':'gender_Male'}>, <AxesSubplot:>]],
dtype=object)
'-------------------------'
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| credit_score | 1.000000 | -0.003679 | -0.007329 | 0.004633 | 0.003031 | 0.003015 | 0.027371 | -0.008305 | -0.007066 | 0.004265 | -0.017097 |
| age | -0.003679 | 1.000000 | -0.011031 | 0.030107 | -0.026815 | -0.018054 | 0.071512 | -0.002584 | 0.058367 | -0.008131 | -0.016069 |
| tenure | -0.007329 | -0.011031 | 1.000000 | -0.007759 | 0.015903 | 0.032542 | -0.028362 | 0.002300 | -0.010290 | -0.000654 | 0.017140 |
| balance | 0.004633 | 0.030107 | -0.007759 | 1.000000 | -0.298937 | -0.021780 | -0.007702 | 0.022207 | 0.406291 | -0.142702 | 0.002475 |
| num_of_products | 0.003031 | -0.026815 | 0.015903 | -0.298937 | 1.000000 | 0.006731 | 0.011066 | 0.014886 | -0.005970 | 0.017134 | -0.015875 |
| has_cr_card | 0.003015 | -0.018054 | 0.032542 | -0.021780 | 0.006731 | 1.000000 | 0.000438 | -0.012973 | 0.006663 | 0.000631 | 0.015197 |
| is_active_member | 0.027371 | 0.071512 | -0.028362 | -0.007702 | 0.011066 | 0.000438 | 1.000000 | -0.019026 | -0.012022 | 0.007164 | 0.018872 |
| estimated_salary | -0.008305 | -0.002584 | 0.002300 | 0.022207 | 0.014886 | -0.012973 | -0.019026 | 1.000000 | 0.032323 | -0.012693 | -0.005145 |
| geography_Germany | -0.007066 | 0.058367 | -0.010290 | 0.406291 | -0.005970 | 0.006663 | -0.012022 | 0.032323 | 1.000000 | -0.332857 | -0.030655 |
| geography_Spain | 0.004265 | -0.008131 | -0.000654 | -0.142702 | 0.017134 | 0.000631 | 0.007164 | -0.012693 | -0.332857 | 1.000000 | 0.008605 |
| gender_Male | -0.017097 | -0.016069 | 0.017140 | 0.002475 | -0.015875 | 0.015197 | 0.018872 | -0.005145 | -0.030655 | 0.008605 | 1.000000 |
'-------------------------'
'-------------------------'
'Информация для колоки'
'credit_score'
count 5454.000000 mean 651.312248 std 95.809994 min 350.000000 25% 585.000000 50% 653.000000 75% 717.000000 max 850.000000 Name: credit_score, dtype: float64
'-------------------------'
'Уникальные значения:'
'credit_score'
array([350, 351, 365, 367, 373, 376, 382, 399, 401, 405, 407, 408, 410,
411, 413, 414, 415, 416, 417, 418, 419, 420, 421, 424, 425, 426,
427, 428, 429, 430, 431, 432, 434, 435, 436, 437, 438, 439, 440,
443, 444, 445, 446, 447, 449, 450, 451, 452, 453, 454, 455, 457,
458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470,
471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, 483,
484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496,
497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509,
510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521, 522,
523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535,
536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548,
549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561,
562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574,
575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587,
588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600,
601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613,
614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626,
627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639,
640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652,
653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665,
666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678,
679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690, 691,
692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704,
705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717,
718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730,
731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743,
744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756,
757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, 769,
770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782,
783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795,
796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808,
809, 811, 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822,
823, 824, 825, 826, 827, 828, 829, 831, 832, 833, 834, 835, 836,
837, 838, 839, 840, 841, 842, 844, 845, 846, 847, 848, 849, 850],
dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
850 134
655 33
667 32
678 31
686 29
...
408 1
382 1
351 1
414 1
376 1
Name: credit_score, Length: 442, dtype: int64
'-------------------------'
'Пропуски:'
'credit_score'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'age'
count 5454.000000 mean 38.760359 std 10.370681 min 18.000000 25% 32.000000 50% 37.000000 75% 44.000000 max 84.000000 Name: age, dtype: float64
'-------------------------'
'Уникальные значения:'
'age'
array([18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84],
dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
38 264 35 260 37 256 33 240 34 239 36 238 39 236 31 226 32 226 40 221 41 212 29 196 30 182 42 170 43 163 28 155 44 148 46 128 45 128 27 114 26 107 47 99 48 91 25 86 49 83 24 77 50 71 51 60 23 53 52 51 22 48 54 46 60 42 56 41 57 40 53 39 55 39 59 34 58 33 21 30 20 23 63 23 67 22 62 22 61 21 64 21 66 19 18 17 19 11 70 11 69 11 68 11 71 10 72 10 74 8 65 7 73 7 76 6 77 6 75 4 79 3 80 2 84 2 81 2 78 2 82 1 Name: age, dtype: int64
'-------------------------'
'Пропуски:'
'age'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'tenure'
count 5454.000000 mean 4.979831 std 2.877975 min 0.000000 25% 2.000000 50% 5.000000 75% 7.000000 max 10.000000 Name: tenure, dtype: float64
'-------------------------'
'Уникальные значения:'
'tenure'
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
'-------------------------'
'Колличество каждого уникального значения:'
7.0 572 2.0 568 5.0 567 1.0 566 3.0 564 6.0 553 8.0 551 9.0 513 4.0 511 10.0 254 0.0 235 Name: tenure, dtype: int64
'-------------------------'
'Пропуски:'
'tenure'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'balance'
count 5454.000000 mean 77038.626377 std 62334.346709 min 0.000000 25% 0.000000 50% 97504.935000 75% 127659.150000 max 250898.090000 Name: balance, dtype: float64
'-------------------------'
'Уникальные значения:'
'balance'
array([ 0. , 12459.19, 14262.8 , ..., 213146.2 , 216109.88,
250898.09])
'-------------------------'
'Колличество каждого уникального значения:'
0.00 1954
157929.45 1
130181.47 1
121365.39 1
80764.03 1
...
106250.72 1
90145.04 1
124993.29 1
116954.32 1
116322.27 1
Name: balance, Length: 3501, dtype: int64
'-------------------------'
'Пропуски:'
'balance'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'num_of_products'
count 5454.000000 mean 1.525119 std 0.584044 min 1.000000 25% 1.000000 50% 1.000000 75% 2.000000 max 4.000000 Name: num_of_products, dtype: float64
'-------------------------'
'Уникальные значения:'
'num_of_products'
array([1, 2, 3, 4], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 2802 2 2478 3 136 4 38 Name: num_of_products, dtype: int64
'-------------------------'
'Пропуски:'
'num_of_products'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'has_cr_card'
count 5454.000000 mean 0.706087 std 0.455594 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: has_cr_card, dtype: float64
'-------------------------'
'Уникальные значения:'
'has_cr_card'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 3851 0 1603 Name: has_cr_card, dtype: int64
'-------------------------'
'Пропуски:'
'has_cr_card'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'is_active_member'
count 5454.000000 mean 0.513751 std 0.499857 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: is_active_member, dtype: float64
'-------------------------'
'Уникальные значения:'
'is_active_member'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 2802 0 2652 Name: is_active_member, dtype: int64
'-------------------------'
'Пропуски:'
'is_active_member'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'estimated_salary'
count 5454.000000 mean 99500.237437 std 57510.102670 min 11.580000 25% 50667.922500 50% 99009.130000 75% 148786.377500 max 199953.330000 Name: estimated_salary, dtype: float64
'-------------------------'
'Уникальные значения:'
'estimated_salary'
array([1.1580000e+01, 9.0070000e+01, 9.1750000e+01, ..., 1.9985747e+05,
1.9986275e+05, 1.9995333e+05])
'-------------------------'
'Колличество каждого уникального значения:'
109145.20 1
103792.53 1
101986.16 1
26257.01 1
17675.36 1
..
136859.55 1
168261.40 1
13228.93 1
128384.11 1
102638.35 1
Name: estimated_salary, Length: 5454, dtype: int64
'-------------------------'
'Пропуски:'
'estimated_salary'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'geography_Germany'
count 5454.000000 mean 0.257792 std 0.437459 min 0.000000 25% 0.000000 50% 0.000000 75% 1.000000 max 1.000000 Name: geography_Germany, dtype: float64
'-------------------------'
'Уникальные значения:'
'geography_Germany'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
0 4048 1 1406 Name: geography_Germany, dtype: int64
'-------------------------'
'Пропуски:'
'geography_Germany'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'geography_Spain'
count 5454.000000 mean 0.241841 std 0.428238 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1.000000 Name: geography_Spain, dtype: float64
'-------------------------'
'Уникальные значения:'
'geography_Spain'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
0 4135 1 1319 Name: geography_Spain, dtype: int64
'-------------------------'
'Пропуски:'
'geography_Spain'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'gender_Male'
count 5454.000000 mean 0.545105 std 0.498007 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: gender_Male, dtype: float64
'-------------------------'
'Уникальные значения:'
'gender_Male'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
1 2973 0 2481 Name: gender_Male, dtype: int64
'-------------------------'
'Пропуски:'
'gender_Male'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
data_frame_info(features_valid)
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 2228 | 644 | 37 | 8.0 | 0.00 | 2 | 1 | 0 | 20968.88 | 0 | 0 | 0 |
| 7188 | 708 | 34 | 4.0 | 0.00 | 1 | 1 | 1 | 62868.33 | 0 | 0 | 0 |
| 168 | 667 | 39 | 2.0 | 0.00 | 2 | 1 | 0 | 40721.24 | 0 | 1 | 0 |
| 9133 | 705 | 36 | 1.0 | 111629.29 | 1 | 1 | 1 | 21807.16 | 0 | 1 | 1 |
| 9204 | 646 | 30 | 5.0 | 0.00 | 2 | 1 | 0 | 13935.32 | 0 | 0 | 0 |
'-------------------------'
'Количество явных дубликатов:'
0
'-------------------------'
<class 'pandas.core.frame.DataFrame'> Int64Index: 1818 entries, 2228 to 6205 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 credit_score 1818 non-null int64 1 age 1818 non-null int64 2 tenure 1818 non-null float64 3 balance 1818 non-null float64 4 num_of_products 1818 non-null int64 5 has_cr_card 1818 non-null int64 6 is_active_member 1818 non-null int64 7 estimated_salary 1818 non-null float64 8 geography_Germany 1818 non-null int32 9 geography_Spain 1818 non-null int32 10 gender_Male 1818 non-null int32 dtypes: float64(3), int32(3), int64(5) memory usage: 149.1 KB
None
'-------------------------'
array([[<AxesSubplot:title={'center':'credit_score'}>,
<AxesSubplot:title={'center':'age'}>,
<AxesSubplot:title={'center':'tenure'}>],
[<AxesSubplot:title={'center':'balance'}>,
<AxesSubplot:title={'center':'num_of_products'}>,
<AxesSubplot:title={'center':'has_cr_card'}>],
[<AxesSubplot:title={'center':'is_active_member'}>,
<AxesSubplot:title={'center':'estimated_salary'}>,
<AxesSubplot:title={'center':'geography_Germany'}>],
[<AxesSubplot:title={'center':'geography_Spain'}>,
<AxesSubplot:title={'center':'gender_Male'}>, <AxesSubplot:>]],
dtype=object)
'-------------------------'
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| credit_score | 1.000000 | -0.017967 | 0.011371 | -0.007716 | 0.027334 | -0.032377 | 0.063564 | 0.066390 | 0.028791 | -0.002512 | 0.018312 |
| age | -0.017967 | 1.000000 | 0.016463 | 0.031123 | -0.022191 | -0.004509 | 0.105194 | -0.036407 | 0.031527 | -0.001333 | -0.016432 |
| tenure | 0.011371 | 0.016463 | 1.000000 | -0.023194 | 0.012758 | 0.026370 | -0.011046 | 0.018162 | 0.018920 | -0.007902 | 0.014368 |
| balance | -0.007716 | 0.031123 | -0.023194 | 1.000000 | -0.299169 | -0.001760 | 0.002331 | 0.014727 | 0.394823 | -0.103341 | 0.044682 |
| num_of_products | 0.027334 | -0.022191 | 0.012758 | -0.299169 | 1.000000 | 0.003707 | 0.014329 | 0.006751 | -0.026415 | -0.007575 | -0.037365 |
| has_cr_card | -0.032377 | -0.004509 | 0.026370 | -0.001760 | 0.003707 | 1.000000 | -0.015673 | 0.022679 | 0.010731 | -0.043535 | -0.011424 |
| is_active_member | 0.063564 | 0.105194 | -0.011046 | 0.002331 | 0.014329 | -0.015673 | 1.000000 | -0.023141 | -0.016895 | 0.023575 | 0.039770 |
| estimated_salary | 0.066390 | -0.036407 | 0.018162 | 0.014727 | 0.006751 | 0.022679 | -0.023141 | 1.000000 | -0.037778 | 0.008860 | 0.000059 |
| geography_Germany | 0.028791 | 0.031527 | 0.018920 | 0.394823 | -0.026415 | 0.010731 | -0.016895 | -0.037778 | 1.000000 | -0.324457 | -0.002218 |
| geography_Spain | -0.002512 | -0.001333 | -0.007902 | -0.103341 | -0.007575 | -0.043535 | 0.023575 | 0.008860 | -0.324457 | 1.000000 | 0.022893 |
| gender_Male | 0.018312 | -0.016432 | 0.014368 | 0.044682 | -0.037365 | -0.011424 | 0.039770 | 0.000059 | -0.002218 | 0.022893 | 1.000000 |
'-------------------------'
'-------------------------'
'Информация для колоки'
'credit_score'
count 1818.000000 mean 648.581408 std 97.736672 min 350.000000 25% 583.000000 50% 650.000000 75% 716.000000 max 850.000000 Name: credit_score, dtype: float64
'-------------------------'
'Уникальные значения:'
'credit_score'
array([350, 358, 363, 383, 404, 405, 411, 412, 413, 414, 415, 416, 418,
422, 425, 427, 428, 429, 430, 431, 432, 433, 434, 435, 437, 439,
441, 443, 444, 445, 446, 447, 448, 450, 451, 455, 456, 457, 458,
464, 465, 467, 469, 471, 472, 473, 474, 475, 476, 477, 478, 479,
480, 481, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493,
494, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507,
508, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, 521,
522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534,
535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547,
548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560,
561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573,
574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586,
587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599,
600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612,
613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625,
626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638,
639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651,
652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664,
665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677,
678, 679, 680, 681, 682, 683, 684, 685, 686, 687, 688, 689, 690,
691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, 703,
704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716,
717, 718, 719, 720, 721, 722, 724, 725, 726, 727, 728, 729, 730,
731, 732, 733, 734, 735, 736, 737, 738, 739, 740, 741, 742, 743,
744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756,
757, 758, 759, 760, 761, 762, 764, 765, 766, 767, 769, 770, 771,
772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784,
786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798,
799, 802, 803, 804, 805, 807, 808, 809, 810, 811, 812, 814, 815,
816, 818, 819, 820, 821, 822, 824, 825, 826, 828, 832, 833, 834,
837, 838, 841, 842, 843, 844, 845, 849, 850], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
850 38
678 20
660 13
625 12
637 12
..
439 1
441 1
443 1
444 1
350 1
Name: credit_score, Length: 399, dtype: int64
'-------------------------'
'Пропуски:'
'credit_score'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'age'
count 1818.000000 mean 39.358086 std 10.796254 min 18.000000 25% 32.000000 50% 38.000000 75% 44.000000 max 92.000000 Name: age, dtype: float64
'-------------------------'
'Уникальные значения:'
'age'
array([18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 83, 88, 92],
dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
39 92 37 88 38 87 36 87 33 86 35 85 40 78 34 77 31 69 41 65 32 65 42 65 43 58 29 57 30 53 27 48 46 46 45 44 44 41 26 40 28 39 48 36 50 30 47 28 51 26 49 25 24 23 25 18 23 18 52 18 61 16 57 13 54 13 22 12 62 12 58 12 53 12 56 11 55 10 21 10 67 9 20 8 66 8 69 7 19 6 59 6 74 6 63 6 71 6 64 5 72 5 60 4 65 4 70 3 68 3 75 3 76 3 78 2 77 2 18 2 73 1 79 1 80 1 81 1 83 1 88 1 92 1 Name: age, dtype: int64
'-------------------------'
'Пропуски:'
'age'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'tenure'
count 1818.000000 mean 5.004950 std 2.928515 min 0.000000 25% 3.000000 50% 5.000000 75% 8.000000 max 10.000000 Name: tenure, dtype: float64
'-------------------------'
'Уникальные значения:'
'tenure'
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
'-------------------------'
'Колличество каждого уникального значения:'
1.0 192 3.0 191 8.0 191 4.0 190 9.0 189 2.0 180 7.0 177 5.0 174 6.0 161 10.0 91 0.0 82 Name: tenure, dtype: int64
'-------------------------'
'Пропуски:'
'tenure'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'balance'
count 1818.000000 mean 75002.120946 std 62189.450404 min 0.000000 25% 0.000000 50% 95924.970000 75% 127419.972500 max 221532.800000 Name: balance, dtype: float64
'-------------------------'
'Уникальные значения:'
'balance'
array([ 0. , 3768.69, 27288.43, ..., 199689.49, 206868.78,
221532.8 ])
'-------------------------'
'Колличество каждого уникального значения:'
0.00 670
101278.25 1
66483.32 1
87116.71 1
151675.65 1
...
131142.53 1
136659.74 1
206868.78 1
115961.58 1
153393.18 1
Name: balance, Length: 1149, dtype: int64
'-------------------------'
'Пропуски:'
'balance'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'num_of_products'
count 1818.000000 mean 1.543454 std 0.576110 min 1.000000 25% 1.000000 50% 2.000000 75% 2.000000 max 4.000000 Name: num_of_products, dtype: float64
'-------------------------'
'Уникальные значения:'
'num_of_products'
array([1, 2, 3, 4], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 898 2 860 3 52 4 8 Name: num_of_products, dtype: int64
'-------------------------'
'Пропуски:'
'num_of_products'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'has_cr_card'
count 1818.000000 mean 0.692519 std 0.461577 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: has_cr_card, dtype: float64
'-------------------------'
'Уникальные значения:'
'has_cr_card'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 1259 0 559 Name: has_cr_card, dtype: int64
'-------------------------'
'Пропуски:'
'has_cr_card'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'is_active_member'
count 1818.000000 mean 0.487349 std 0.499977 min 0.000000 25% 0.000000 50% 0.000000 75% 1.000000 max 1.000000 Name: is_active_member, dtype: float64
'-------------------------'
'Уникальные значения:'
'is_active_member'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
0 932 1 886 Name: is_active_member, dtype: int64
'-------------------------'
'Пропуски:'
'is_active_member'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'estimated_salary'
count 1818.000000 mean 101081.887090 std 57155.655331 min 178.190000 25% 52429.995000 50% 100812.790000 75% 148917.515000 max 199992.480000 Name: estimated_salary, dtype: float64
'-------------------------'
'Уникальные значения:'
'estimated_salary'
array([1.7819000e+02, 2.1627000e+02, 2.8799000e+02, ..., 1.9976129e+05,
1.9992917e+05, 1.9999248e+05])
'-------------------------'
'Колличество каждого уникального значения:'
101397.86 1
29761.29 1
181610.60 1
145936.28 1
75578.67 1
..
179269.79 1
75632.78 1
24677.54 1
141946.92 1
72274.84 1
Name: estimated_salary, Length: 1818, dtype: int64
'-------------------------'
'Пропуски:'
'estimated_salary'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'geography_Germany'
count 1818.000000 mean 0.237624 std 0.425745 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1.000000 Name: geography_Germany, dtype: float64
'-------------------------'
'Уникальные значения:'
'geography_Germany'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
0 1386 1 432 Name: geography_Germany, dtype: int64
'-------------------------'
'Пропуски:'
'geography_Germany'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'geography_Spain'
count 1818.000000 mean 0.252475 std 0.434552 min 0.000000 25% 0.000000 50% 0.000000 75% 1.000000 max 1.000000 Name: geography_Spain, dtype: float64
'-------------------------'
'Уникальные значения:'
'geography_Spain'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
0 1359 1 459 Name: geography_Spain, dtype: int64
'-------------------------'
'Пропуски:'
'geography_Spain'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'gender_Male'
count 1818.000000 mean 0.562156 std 0.496258 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: gender_Male, dtype: float64
'-------------------------'
'Уникальные значения:'
'gender_Male'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
1 1022 0 796 Name: gender_Male, dtype: int64
'-------------------------'
'Пропуски:'
'gender_Male'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
data_frame_info(features_test)
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 7273 | 660 | 38 | 6.0 | 109869.32 | 1 | 1 | 1 | 154641.91 | 0 | 1 | 1 |
| 2604 | 540 | 25 | 5.0 | 116160.23 | 1 | 1 | 0 | 13411.67 | 0 | 0 | 1 |
| 9542 | 644 | 37 | 9.0 | 0.00 | 2 | 1 | 1 | 96442.86 | 0 | 1 | 1 |
| 6721 | 824 | 77 | 3.0 | 27517.15 | 2 | 0 | 1 | 2746.41 | 1 | 0 | 1 |
| 744 | 650 | 60 | 8.0 | 0.00 | 2 | 1 | 1 | 102925.76 | 0 | 0 | 1 |
'-------------------------'
'Количество явных дубликатов:'
0
'-------------------------'
<class 'pandas.core.frame.DataFrame'> Int64Index: 1819 entries, 7273 to 9616 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 credit_score 1819 non-null int64 1 age 1819 non-null int64 2 tenure 1819 non-null float64 3 balance 1819 non-null float64 4 num_of_products 1819 non-null int64 5 has_cr_card 1819 non-null int64 6 is_active_member 1819 non-null int64 7 estimated_salary 1819 non-null float64 8 geography_Germany 1819 non-null int32 9 geography_Spain 1819 non-null int32 10 gender_Male 1819 non-null int32 dtypes: float64(3), int32(3), int64(5) memory usage: 149.2 KB
None
'-------------------------'
array([[<AxesSubplot:title={'center':'credit_score'}>,
<AxesSubplot:title={'center':'age'}>,
<AxesSubplot:title={'center':'tenure'}>],
[<AxesSubplot:title={'center':'balance'}>,
<AxesSubplot:title={'center':'num_of_products'}>,
<AxesSubplot:title={'center':'has_cr_card'}>],
[<AxesSubplot:title={'center':'is_active_member'}>,
<AxesSubplot:title={'center':'estimated_salary'}>,
<AxesSubplot:title={'center':'geography_Germany'}>],
[<AxesSubplot:title={'center':'geography_Spain'}>,
<AxesSubplot:title={'center':'gender_Male'}>, <AxesSubplot:>]],
dtype=object)
'-------------------------'
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| credit_score | 1.000000 | 0.007739 | 0.009746 | 0.007268 | 0.019604 | 0.003812 | 0.007206 | -0.015335 | -0.006182 | -0.001028 | -0.009357 |
| age | 0.007739 | 1.000000 | -0.049370 | 0.037211 | -0.057679 | -0.011727 | 0.091825 | 0.007477 | 0.041367 | -0.004299 | -0.063278 |
| tenure | 0.009746 | -0.049370 | 1.000000 | 0.007149 | -0.000843 | 0.012067 | -0.066016 | 0.026438 | -0.003929 | 0.013085 | -0.002294 |
| balance | 0.007268 | 0.037211 | 0.007149 | 1.000000 | -0.312872 | -0.029152 | 0.003106 | -0.008472 | 0.393998 | -0.153041 | 0.038980 |
| num_of_products | 0.019604 | -0.057679 | -0.000843 | -0.312872 | 1.000000 | 0.005746 | -0.001204 | 0.017667 | -0.002494 | 0.014994 | -0.042042 |
| has_cr_card | 0.003812 | -0.011727 | 0.012067 | -0.029152 | 0.005746 | 1.000000 | -0.032803 | -0.014457 | 0.014248 | -0.046253 | -0.002763 |
| is_active_member | 0.007206 | 0.091825 | -0.066016 | 0.003106 | -0.001204 | -0.032803 | 1.000000 | -0.020709 | -0.032877 | 0.027448 | 0.031759 |
| estimated_salary | -0.015335 | 0.007477 | 0.026438 | -0.008472 | 0.017667 | -0.014457 | -0.020709 | 1.000000 | -0.001896 | -0.026500 | 0.001772 |
| geography_Germany | -0.006182 | 0.041367 | -0.003929 | 0.393998 | -0.002494 | 0.014248 | -0.032877 | -0.001896 | 1.000000 | -0.340911 | -0.022622 |
| geography_Spain | -0.001028 | -0.004299 | 0.013085 | -0.153041 | 0.014994 | -0.046253 | 0.027448 | -0.026500 | -0.340911 | 1.000000 | 0.027817 |
| gender_Male | -0.009357 | -0.063278 | -0.002294 | 0.038980 | -0.042042 | -0.002763 | 0.031759 | 0.001772 | -0.022622 | 0.027817 | 1.000000 |
'-------------------------'
'-------------------------'
'Информация для колоки'
'credit_score'
count 1819.000000 mean 651.164376 std 96.893003 min 350.000000 25% 584.000000 50% 652.000000 75% 719.000000 max 850.000000 Name: credit_score, dtype: float64
'-------------------------'
'Уникальные значения:'
'credit_score'
array([350, 386, 395, 408, 411, 415, 418, 421, 422, 430, 431, 432, 433,
434, 436, 437, 438, 439, 442, 443, 445, 446, 447, 448, 450, 452,
453, 454, 457, 458, 459, 460, 464, 465, 466, 467, 468, 469, 471,
473, 475, 476, 477, 479, 480, 481, 482, 483, 484, 485, 486, 487,
488, 489, 490, 492, 493, 494, 495, 497, 498, 499, 500, 501, 502,
503, 504, 505, 506, 507, 509, 510, 511, 512, 513, 514, 515, 516,
517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529,
530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542,
543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 554, 555, 556,
557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569,
570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582,
583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595,
596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608,
609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621,
622, 623, 624, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634,
635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647,
648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660,
661, 662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673,
674, 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, 686,
687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699,
700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712,
713, 714, 715, 716, 717, 718, 719, 720, 721, 722, 723, 724, 725,
726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738,
739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751,
752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764,
765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777,
778, 779, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791,
792, 793, 794, 795, 796, 797, 799, 800, 801, 802, 803, 804, 805,
806, 807, 809, 810, 812, 813, 814, 815, 817, 818, 819, 820, 821,
822, 823, 824, 827, 828, 829, 830, 831, 832, 833, 834, 835, 838,
839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850],
dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
850 38
584 14
586 13
683 13
640 12
..
465 1
466 1
467 1
408 1
578 1
Name: credit_score, Length: 402, dtype: int64
'-------------------------'
'Пропуски:'
'credit_score'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'age'
count 1819.000000 mean 39.106652 std 10.849589 min 18.000000 25% 32.000000 50% 37.000000 75% 44.000000 max 85.000000 Name: age, dtype: float64
'-------------------------'
'Уникальные значения:'
'age'
array([18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
69, 70, 71, 72, 73, 74, 76, 77, 81, 85], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
34 96 37 91 35 86 40 84 32 83 33 77 36 76 38 73 31 64 42 62 30 61 41 61 29 60 39 59 28 53 43 44 44 44 46 39 25 38 26 35 45 35 47 34 27 33 48 29 24 25 49 25 51 25 52 23 23 21 22 20 57 20 55 19 50 19 58 18 54 16 59 16 53 15 62 14 61 12 60 11 56 10 71 9 64 9 19 9 21 8 63 7 20 6 66 6 73 5 72 5 65 5 67 5 69 4 70 3 68 3 74 2 76 2 77 2 81 1 18 1 85 1 Name: age, dtype: int64
'-------------------------'
'Пропуски:'
'age'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'tenure'
count 1819.00000 mean 5.04398 std 2.91184 min 0.00000 25% 2.00000 50% 5.00000 75% 8.00000 max 10.00000 Name: tenure, dtype: float64
'-------------------------'
'Уникальные значения:'
'tenure'
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
'-------------------------'
'Колличество каждого уникального значения:'
2.0 202 1.0 194 8.0 191 5.0 186 4.0 184 9.0 180 7.0 176 3.0 173 6.0 167 10.0 101 0.0 65 Name: tenure, dtype: int64
'-------------------------'
'Пропуски:'
'tenure'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'balance'
count 1819.000000 mean 76495.714865 std 62464.099959 min 0.000000 25% 0.000000 50% 97565.740000 75% 127281.045000 max 238387.560000 Name: balance, dtype: float64
'-------------------------'
'Уникальные значения:'
'balance'
array([ 0. , 27517.15, 33563.95, ..., 214346.96, 222267.63,
238387.56])
'-------------------------'
'Колличество каждого уникального значения:'
0.00 659
124548.99 1
71902.52 1
155931.11 1
138333.03 1
...
97259.25 1
64345.21 1
130809.77 1
118114.28 1
105303.73 1
Name: balance, Length: 1161, dtype: int64
'-------------------------'
'Пропуски:'
'balance'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'num_of_products'
count 1819.000000 mean 1.532161 std 0.576811 min 1.000000 25% 1.000000 50% 1.000000 75% 2.000000 max 4.000000 Name: num_of_products, dtype: float64
'-------------------------'
'Уникальные значения:'
'num_of_products'
array([1, 2, 3, 4], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 917 2 846 3 46 4 10 Name: num_of_products, dtype: int64
'-------------------------'
'Пропуски:'
'num_of_products'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'has_cr_card'
count 1819.000000 mean 0.714129 std 0.451953 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: has_cr_card, dtype: float64
'-------------------------'
'Уникальные значения:'
'has_cr_card'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 1299 0 520 Name: has_cr_card, dtype: int64
'-------------------------'
'Пропуски:'
'has_cr_card'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'is_active_member'
count 1819.000000 mean 0.549203 std 0.497710 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: is_active_member, dtype: float64
'-------------------------'
'Уникальные значения:'
'is_active_member'
array([0, 1], dtype=int64)
'-------------------------'
'Колличество каждого уникального значения:'
1 999 0 820 Name: is_active_member, dtype: int64
'-------------------------'
'Пропуски:'
'is_active_member'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'estimated_salary'
count 1819.000000 mean 101322.847257 std 58430.876870 min 123.070000 25% 52554.470000 50% 102472.900000 75% 152306.660000 max 199970.740000 Name: estimated_salary, dtype: float64
'-------------------------'
'Уникальные значения:'
'estimated_salary'
array([1.2307000e+02, 4.1741000e+02, 4.4773000e+02, ..., 1.9980563e+05,
1.9990932e+05, 1.9997074e+05])
'-------------------------'
'Колличество каждого уникального значения:'
132908.07 1
120892.96 1
13455.43 1
5924.38 1
9561.73 1
..
187758.50 1
85869.89 1
161671.15 1
106282.74 1
92320.37 1
Name: estimated_salary, Length: 1819, dtype: int64
'-------------------------'
'Пропуски:'
'estimated_salary'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'geography_Germany'
count 1819.000000 mean 0.250137 std 0.433211 min 0.000000 25% 0.000000 50% 0.000000 75% 0.500000 max 1.000000 Name: geography_Germany, dtype: float64
'-------------------------'
'Уникальные значения:'
'geography_Germany'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
0 1364 1 455 Name: geography_Germany, dtype: int64
'-------------------------'
'Пропуски:'
'geography_Germany'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'geography_Spain'
count 1819.000000 mean 0.258384 std 0.437866 min 0.000000 25% 0.000000 50% 0.000000 75% 1.000000 max 1.000000 Name: geography_Spain, dtype: float64
'-------------------------'
'Уникальные значения:'
'geography_Spain'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
0 1349 1 470 Name: geography_Spain, dtype: int64
'-------------------------'
'Пропуски:'
'geography_Spain'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
'-------------------------'
'Информация для колоки'
'gender_Male'
count 1819.000000 mean 0.538208 std 0.498675 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: gender_Male, dtype: float64
'-------------------------'
'Уникальные значения:'
'gender_Male'
array([0, 1])
'-------------------------'
'Колличество каждого уникального значения:'
1 979 0 840 Name: gender_Male, dtype: int64
'-------------------------'
'Пропуски:'
'gender_Male'
': кол-во NaN'
0
', процент NaN'
0.0
'%'
target_train.describe()
count 5454.000000 mean 0.203887 std 0.402923 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1.000000 Name: exited, dtype: float64
target_valid.describe()
count 1818.000000 mean 0.204070 std 0.403132 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1.000000 Name: exited, dtype: float64
target_test.describe()
count 1819.000000 mean 0.203958 std 0.403049 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1.000000 Name: exited, dtype: float64
Выводы:
Одним из методов маштабирования является стандартизация данных. При ее использовании мы получим все признаки со средними равными 0 и дисперсией равной 1. Но это на обучающей выборке. На остальных выборках, возможны не большие отклонения.
# все колонки в обучающей выборке
features_test.columns
Index(['credit_score', 'age', 'tenure', 'balance', 'num_of_products',
'has_cr_card', 'is_active_member', 'estimated_salary',
'geography_Germany', 'geography_Spain', 'gender_Male'],
dtype='object')
Сохраним колонки, которые будем маштабировать в список numeric_for_scaler.
# запишем признаки, которые ножно маштабировать
numeric_for_scaler = ['credit_score', 'age', 'tenure', 'balance', 'num_of_products', 'estimated_salary']
# создадим и обучим модель, которая сделает для нас стандартизацю, на учебной выборке
scaler = StandardScaler()
scaler.fit(features_train[numeric_for_scaler])
# создадим дубликаты, чтобы в них сохранить результаты стандартизации
features_train_scaler = features_train.copy(deep=True)
features_valid_scaler = features_valid.copy(deep=True)
features_test_scaler = features_test.copy(deep=True)
# стандартизируем все выборки с не целевыми признаками
features_train_scaler[numeric_for_scaler] = scaler.transform(features_train[numeric_for_scaler])
features_valid_scaler[numeric_for_scaler] = scaler.transform(features_valid[numeric_for_scaler])
features_test_scaler[numeric_for_scaler] = scaler.transform(features_test[numeric_for_scaler])
Посмотрим на результаты.
features_train.head()
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 4736 | 638 | 32 | 0.0 | 0.00 | 2 | 1 | 0 | 160129.99 | 0 | 0 | 0 |
| 873 | 561 | 28 | 7.0 | 0.00 | 2 | 1 | 0 | 7797.01 | 0 | 0 | 1 |
| 2093 | 555 | 55 | 4.0 | 146798.81 | 1 | 1 | 1 | 74149.77 | 0 | 0 | 1 |
| 6859 | 733 | 38 | 3.0 | 157658.36 | 1 | 0 | 0 | 19658.43 | 0 | 0 | 0 |
| 7624 | 728 | 69 | 1.0 | 0.00 | 2 | 1 | 1 | 131804.86 | 0 | 0 | 0 |
features_train_scaler.head()
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 4736 | -0.138957 | -0.651932 | -1.730483 | -1.236007 | 0.813165 | 1 | 0 | 1.054342 | 0 | 0 | 0 |
| 873 | -0.942705 | -1.037670 | 0.702005 | -1.236007 | 0.813165 | 1 | 0 | -1.594705 | 0 | 0 | 1 |
| 2093 | -1.005334 | 1.566062 | -0.340490 | 1.119232 | -0.899191 | 1 | 1 | -0.440841 | 0 | 0 | 1 |
| 6859 | 0.852680 | -0.073325 | -0.687988 | 1.293462 | -0.899191 | 0 | 0 | -1.388437 | 0 | 0 | 0 |
| 7624 | 0.800488 | 2.916145 | -1.382985 | -1.236007 | 0.813165 | 1 | 1 | 0.561772 | 0 | 0 | 0 |
features_valid_scaler.sample()
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 8347 | 1.625113 | -1.03767 | 0.702005 | 1.426802 | 2.525522 | 1 | 0 | 0.984646 | 0 | 1 | 0 |
features_test_scaler.sample()
| credit_score | age | tenure | balance | num_of_products | has_cr_card | is_active_member | estimated_salary | geography_Germany | geography_Spain | gender_Male | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 6426 | 0.957063 | 0.601717 | 0.702005 | 1.28823 | -0.899191 | 1 | 0 | 0.450817 | 0 | 1 | 1 |
Вывод:
Памятка:
feature_train - тренировочная обучающая выборка без маштабирования
features_train_scaler - тренировочная обучающая выборка с маштабированием
Аналогично для для проверочной и тестовой обучающих выборок.
Для исследования мы должны выбрать метрику или метрики, которые будет определять качество модели.
Для этого проекта главной метрикой будет являться F1-мера, которая является средним гармоническим полноты( Recall) и точности(Precision).
Наша цель найти модель со значением F1-меры большим 0,59.
Дополнительной метрикой будет AUC-ROC, это площадь под кривой ошибок (ROC-кривой: TPR и FPR в зависимости от treshold). Значение этой метрики для случайной модели 0,5.
Но мы оставляем за собой право применять и другие инструменты по мере необходимости.
Напишем функцию для F1-меры.
# функция получает обученную модель, обучающие признаки, целевой признак
# выдате F1-меру
def f_one (model, features_v, target_v):
predicted_v = model.predict(features_v)
return f1_score(target_v, predicted_v)
Напишем функцию для меры AUC-ROC.
# функция получает обученную модель, обучающие признаки, целевой признак
# выдате меру AUC-ROC
def auc_roc (model, features_v, target_v):
# найдем вероятность положительного класса
probabilities_v = model.predict_proba(features_v)
probabilities_one_v = probabilities_v[:, 1]
# посчитаем метрику AUC-ROC
return roc_auc_score(target_v, probabilities_one_v)
Напишем функцию для рисования PR кривой.
Важно, что Recall будет отражаться на вертикальной оси, как TPR на ROC-кривой.
def my_pr_curve (model, features_v, target_v):
probabilities_valid = model.predict_proba(features_v)
probabilities_one_valid = probabilities_valid[:, 1]
precision, recall, thresholds = precision_recall_curve(target_v, probabilities_one_valid)
# plt.figure()
plt.figure(figsize=(6, 6))
# plt.step(precision, recall, where='post')
# recall будет отражаться по оси Y чтобы было аналогично с TPR на ROC кривой.
plt.plot(precision, recall)
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel("Precision")
plt.ylabel("True Positive Rate - Recall")
plt.title('PR- кривая (транспонированная)')
plt.show
Напишем функцию для рисования ROC кривой.
def my_roc_curve (model, features_v, target_v):
probabilities_valid = model.predict_proba(features_v)
probabilities_one_valid = probabilities_valid[:, 1]
fpr, tpr, thresholds = roc_curve(target_v, probabilities_one_valid)
plt.figure(figsize=(6, 6))
plt.plot(fpr, tpr)
# ROC-кривая случайной модели (выглядит как прямая)
plt.plot([0, 1], [0, 1], linestyle='--')
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate - Recall")
plt.title("ROC-кривая")
plt.show()
В этом проекте мы будем использовать модели классификаторы: Логистическая регрессия, Дерево Решений, Случайный Лес.
Напомним, что целевой признак не сбалансирован.
Рассмотрим модель Логистической регрессии на настройках по умолчанию при работе на не маштабированных признаках.
model = LogisticRegression(random_state=rs
# , solver='liblinear'
)
model.fit(features_train, target_train)
# predicted_valid = model.predict(features_valid)
# f1_score(target_valid, predicted_valid)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid, target_valid),
auc_roc(model, features_valid, target_valid)))
'F1-мера = 0.11685 | AUC-ROC = 0.63214'
Такое значение F1 говорит о том, что прогноз класса не удался. Причем совсем. AUC-ROC говорит, что модель работает лучше чем случайная.
my_pr_curve(model,features_valid, target_valid)
my_roc_curve(model,features_valid, target_valid)
Рассмотрим модель Логистической регрессии на настройках по умолчанию при работе на маштабированных признаках.
model = LogisticRegression(random_state=rs
# , solver='liblinear'
)
model.fit(features_train_scaler, target_train)
# predicted_valid = model.predict(features_valid)
# f1_score(target_valid, predicted_valid)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid, target_valid),
auc_roc(model, features_valid, target_valid)))
'F1-мера = 0.33897 | AUC-ROC = 0.50104'
Такое F1 значение говорит о том, что прогноз класса не удался. AUC-ROC говорит, что модель работает почти как случайная.
my_pr_curve(model,features_valid, target_valid)
my_roc_curve(model,features_valid, target_valid)
Вывод:
Возьмем алгоритм улучшения из предыдушего проекта и немного его доработаем под текущие задачи. Будем использовать только маштабированные признаки.
# составим списки значений для параметров, которые мы хотим перебрать для поиска наилучшей модели
solver_list = ['lbfgs', 'liblinear', 'newton-cg', 'newton-cholesky', 'sag', 'saga']
c_list = [1, 0.1, 0.2, 0.3 , 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
penalty_list = ['l2', 'l1', 'elasticnet', None]
l1_ratio_list = [None, 0.1, 0.2, 0.3 , 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] # Только, если penalty = 'elasticnet'
best_result=0
# циклами по этим параметрам дойдем до
for solver_value in solver_list:
for c_value in c_list:
for penalty_value in penalty_list:
if penalty_value == 'elasticnet':
for l1_ratio_value in l1_ratio_list:
try: # схалявим и вместо изучения всех зависимостей параметров воспользуемся try except
model = LogisticRegression( random_state=rs # создания модели без параметры class_weight
, solver=solver_value
, C=c_value
, penalty=penalty_value
,l1_ratio=l1_ratio_value
)
model.fit(features_train_scaler, target_train) # обучения модели и проверки ее характеристик
predicted_valid = model.predict(features_valid_scaler)
result = f1_score(target_valid, predicted_valid) # посчитайте качество модели на валидационной выборке
except:
result = 0
if result > best_result:
best_model_logistic_regression = model # сохраните наилучшую модель
best_result = result # сохраните наилучшее значение метрики accuracy на валидационных данных
best_solver_value = solver_value
best_c_value = c_value
best_penalty_value = penalty_value
best_l1_ratio_value = l1_ratio_value
else:
try:
model = LogisticRegression(random_state=rs
, solver=solver_value
, C=c_value
, penalty=penalty_value
)
model.fit(features_train_scaler, target_train)
predicted_valid = model.predict(features_valid_scaler)
result = f1_score(target_valid, predicted_valid) # посчитайте качество модели на валидационной выборке
except:
result = 0
if result > best_result:
best_model_logistic_regression = model # сохраните наилучшую модель
best_result = result # сохраните наилучшее значение метрики accuracy на валидационных данных
best_solver_value = solver_value
best_c_value = c_value
best_penalty_value = penalty_value
# выведем наши результаты
display("F1-мера наилучшей модели на проверочной выборке:", best_result)
display('Лучшие параметры:')
display('Solver=', best_solver_value)
display('C=', best_c_value)
display('Penalty=', best_penalty_value)
try:
display('l1_ratio=', best_l1_ratio_value)
except:
display('l1_ratio= None')
'F1-мера наилучшей модели на проверочной выборке:'
0.29514563106796116
'Лучшие параметры:'
'Solver='
'saga'
'C='
1
'Penalty='
'l1'
'l1_ratio= None'
Посмотрим на результат AUC-ROC.
auc_roc(best_model_logistic_regression, features_valid, target_valid)
0.5010366275051832
Выводы:
class_weight для оптимизации модели.Рассмотрим модель Дерево решений на не маштабированных признаках.
model = DecisionTreeClassifier(random_state=rs)
model.fit(features_train, target_train)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid, target_valid),
auc_roc(model, features_valid, target_valid)))
'F1-мера = 0.50455 | AUC-ROC = 0.69096'
Рассмотрим модель Дерево решений на маштабированных признаках.
model = DecisionTreeClassifier(random_state=rs)
model.fit(features_train_scaler, target_train)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.50260 | AUC-ROC = 0.68962'
Выводы:
Постараемся улучшить показатели модели дерева решений за счет увеличения максимальной глубины в пределах 20. Используем маштабированные признаки.
# создадим датафрейм для записи наших наблюдений
stat_decision_tree = pd.DataFrame(columns=['depth', 'f1', 'auc_roc'])
for depth in range(1, 21):
model = DecisionTreeClassifier(max_depth=depth, random_state=rs) # задаем параметры модели
# обучаем модель
model.fit(features_train_scaler, target_train)
# записываем данные в датафрейм
stat_decision_tree = stat_decision_tree.append({'depth': depth
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
# выводим собранные данные
stat_decision_tree.sort_values(by='f1', ascending=False)
| depth | f1 | auc_roc | |
|---|---|---|---|
| 7 | 8.0 | 0.539535 | 0.805573 |
| 6 | 7.0 | 0.526316 | 0.815569 |
| 9 | 10.0 | 0.522936 | 0.757026 |
| 10 | 11.0 | 0.521610 | 0.738979 |
| 8 | 9.0 | 0.517915 | 0.770197 |
| 12 | 13.0 | 0.515320 | 0.714891 |
| 11 | 12.0 | 0.514970 | 0.719922 |
| 5 | 6.0 | 0.508651 | 0.808473 |
| 14 | 15.0 | 0.504828 | 0.708567 |
| 15 | 16.0 | 0.503259 | 0.710410 |
| 18 | 19.0 | 0.502604 | 0.696843 |
| 16 | 17.0 | 0.501326 | 0.705471 |
| 17 | 18.0 | 0.501305 | 0.703881 |
| 3 | 4.0 | 0.487136 | 0.787273 |
| 19 | 20.0 | 0.487047 | 0.682367 |
| 13 | 14.0 | 0.486639 | 0.703647 |
| 1 | 2.0 | 0.483307 | 0.700889 |
| 4 | 5.0 | 0.471483 | 0.807744 |
| 2 | 3.0 | 0.442344 | 0.759486 |
| 0 | 1.0 | 0.000000 | 0.643314 |
Выводы:
Рассмотрим модель Случайного Леса на настройках по умолчанию с не маштабированными признаками.
model = RandomForestClassifier(random_state=rs)
model.fit(features_train, target_train)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid, target_valid),
auc_roc(model, features_valid, target_valid)))
'F1-мера = 0.54941 | AUC-ROC = 0.81986'
Рассмотрим модель Случайного Леса на настройках по умолчанию с маштабированными признаками.
model = RandomForestClassifier(random_state=rs)
model.fit(features_train_scaler, target_train)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.55184 | AUC-ROC = 0.82016'
Выводы:
Постараемся улучшить модель Случайного леса за счет измениния глубины деревьев и их колличества в пределах 20 по каждому из параметров. Используем маштабированные признаки.
%%time
stat_random_forest = pd.DataFrame(columns=['n_estimators', 'depth', 'f1', 'auc_roc'])
for est in range(1, 21):
for depth in range(1, 21):
model = RandomForestClassifier(n_estimators=est, max_depth=depth, random_state=rs) # обучите модель с заданным количеством деревьев
model.fit(features_train_scaler, target_train) # обучите модель на тренировочной выборке
# собираем данные
stat_random_forest = stat_random_forest.append({'depth': depth
, 'n_estimators': est
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
stat_random_forest.sort_values(by='f1', ascending=False).head()
Wall time: 34.4 s
| n_estimators | depth | f1 | auc_roc | |
|---|---|---|---|---|
| 174 | 9.0 | 15.0 | 0.559350 | 0.795313 |
| 130 | 7.0 | 11.0 | 0.558824 | 0.808168 |
| 169 | 9.0 | 10.0 | 0.556492 | 0.812855 |
| 230 | 12.0 | 11.0 | 0.555740 | 0.811396 |
| 250 | 13.0 | 11.0 | 0.554817 | 0.812032 |
Выводы:
Напомним, что сейчас мы работаем с данными, где обучающие признаки маштабированы. Эффективность показана в разделе выше. И в этом разделе мы используем методы для устранения дисбаланса целевого признака:
Конечно, мы будем применять только те методы, которые позволят модели.
Используем функцию upsamle для получения обучающих выборок для upsampling.
def upsample(features, target, repeat):
features_zeros = features[target == 0]
features_ones = features[target == 1]
target_zeros = target[target == 0]
target_ones = target[target == 1]
features_upsampled = pd.concat([features_zeros] + [features_ones] * repeat)
target_upsampled = pd.concat([target_zeros] + [target_ones] * repeat)
features_upsampled, target_upsampled = shuffle(
features_upsampled, target_upsampled, random_state=rs)
return features_upsampled, target_upsampled
Так как у нас единиц около 20%, а нулей 80%
target_train.mean()
0.2038870553722039
то нам нужно увеличить колличество объектов положительного класса в 4 раза (80/20).
upsample_repeat = int(round((1- target_train.mean()) / target_train.mean(), 0))
upsample_repeat
4
features_train_upsampled, target_train_upsampled = upsample(features_train_scaler, target_train, upsample_repeat)
target_train_upsampled.describe()
count 8790.000000 mean 0.506030 std 0.499992 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: exited, dtype: float64
Вывод: Мы получили пару features_train_upsampled, target_train_upsampled для обучения моделей с балансировкой положительного и отрицательного класса. При этом колличество объектов увеличилось более чем на 3 тысячи штук.
Памятка:
futures_train_upsampled - является обучающей маштабированной выборкой с увеличением объектов по методу upsampling.
Используем функцию downsample для получения обучающих выборок для downsampling.
def downsample(features, target, fraction):
features_zeros = features[target == 0]
features_ones = features[target == 1]
target_zeros = target[target == 0]
target_ones = target[target == 1]
features_downsampled = pd.concat(
[features_zeros.sample(frac=fraction)] + [features_ones])
# target_downsampled = pd.concat(
# [target_zeros.sample(frac=fraction)] + [target_ones])
target_downsampled = pd.Series(target, index=features_downsampled.index)
features_downsampled, target_downsampled = shuffle(
features_downsampled, target_downsampled, random_state=rs)
return features_downsampled, target_downsampled
Так как у нас единиц около 20%, а нулей 80%
target_train.mean()
0.2038870553722039
то нам нужно уменьшить колличество объектов отрицательного класса в 4 раза (80/20).
downsample_repeat = 1 / upsample_repeat
features_train_downsampled, target_train_downsampled = downsample(features_train_scaler, target_train, downsample_repeat)
target_train_downsampled.describe()
count 2198.000000 mean 0.505914 std 0.500079 min 0.000000 25% 0.000000 50% 1.000000 75% 1.000000 max 1.000000 Name: exited, dtype: float64
Вывод: Мы получили пару features_train_downsampled, target_train_downsampled для обучения моделей с балансировкой положительного и отрицательного класса. При этом колличество объектов уменьшилось более чем на 3 тысячи штук.
Памятка:
futures_train_downsampled - является обучающей маштабированной выборкой с уменьшением количества объектов по методу downsampling.
Логистическая регрессия позволяет применить все методы устранения дисбаланса классов в целевом признаке.
Посмотрим на них при настройках по умолчанию и маштабированных признаках.
model = LogisticRegression(class_weight='balanced', random_state=rs
# , solver='liblinear'
)
model.fit(features_train_scaler, target_train)
# predicted_valid = model.predict(features_valid)
# f1_score(target_valid, predicted_valid)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.45336 | AUC-ROC = 0.73296'
model = LogisticRegression(random_state=rs)
model.fit(features_train_upsampled, target_train_upsampled)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.45614 | AUC-ROC = 0.73293'
model = LogisticRegression(random_state=rs)
model.fit(features_train_downsampled, target_train_downsampled)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.45883 | AUC-ROC = 0.73305'
model = LogisticRegression(random_state=rs) # создадим модель
model.fit(features_train_scaler, target_train) # обучим на тренировочной выборке
# найдем вероятность получения положительного класса
probabilities_valid = model.predict_proba(features_valid_scaler)
probabilities_one_valid = probabilities_valid[:, 1]
best_f1 = 0
# best_threshold = 0
# переберем пороги
for threshold in np.arange(0.0, 0.5, 0.02):
predicted_valid = probabilities_one_valid > threshold # наше предсказание Если больше порога, то положительный класс 1=True
precision = precision_score(target_valid, predicted_valid) # считаем точность
recall = recall_score(target_valid, predicted_valid) # считаем полноту
f1 = f1_score(target_valid, predicted_valid) # считаем F1 метрику
print("Порог = {:.2f} | Точность = {:.3f}, Полнота = {:.3f}, F1 мера = {:.3f}".format(
threshold, precision, recall, f1))
if f1 > best_f1: # выбираем лучшую метрику и фиксируем ее порог
best_f1 = f1
best_threshold = threshold
best_predicted_valid = predicted_valid # проверка результатов
display('Лучший результат')
display("Порог = {:.2f} | F1 - мера = {:.5f}".format(best_threshold, best_f1))
# best_predicted_valid.mean() # проверка результатов
Порог = 0.00 | Точность = 0.204, Полнота = 1.000, F1 мера = 0.339 Порог = 0.02 | Точность = 0.205, Полнота = 0.997, F1 мера = 0.340 Порог = 0.04 | Точность = 0.217, Полнота = 0.987, F1 мера = 0.355 Порог = 0.06 | Точность = 0.236, Полнота = 0.965, F1 мера = 0.379 Порог = 0.08 | Точность = 0.251, Полнота = 0.919, F1 мера = 0.394 Порог = 0.10 | Точность = 0.268, Полнота = 0.895, F1 мера = 0.412 Порог = 0.12 | Точность = 0.278, Полнота = 0.833, F1 мера = 0.416 Порог = 0.14 | Точность = 0.294, Полнота = 0.790, F1 мера = 0.429 Порог = 0.16 | Точность = 0.311, Полнота = 0.744, F1 мера = 0.438 Порог = 0.18 | Точность = 0.324, Полнота = 0.701, F1 мера = 0.443 Порог = 0.20 | Точность = 0.338, Полнота = 0.666, F1 мера = 0.449 Порог = 0.22 | Точность = 0.362, Полнота = 0.633, F1 мера = 0.461 Порог = 0.24 | Точность = 0.373, Полнота = 0.590, F1 мера = 0.457 Порог = 0.26 | Точность = 0.387, Полнота = 0.547, F1 мера = 0.454 Порог = 0.28 | Точность = 0.413, Полнота = 0.507, F1 мера = 0.455 Порог = 0.30 | Точность = 0.430, Полнота = 0.482, F1 мера = 0.455 Порог = 0.32 | Точность = 0.440, Полнота = 0.447, F1 мера = 0.444 Порог = 0.34 | Точность = 0.451, Полнота = 0.410, F1 мера = 0.429 Порог = 0.36 | Точность = 0.483, Полнота = 0.391, F1 мера = 0.432 Порог = 0.38 | Точность = 0.487, Полнота = 0.356, F1 мера = 0.411 Порог = 0.40 | Точность = 0.489, Полнота = 0.313, F1 мера = 0.382 Порог = 0.42 | Точность = 0.505, Полнота = 0.294, F1 мера = 0.371 Порог = 0.44 | Точность = 0.500, Полнота = 0.267, F1 мера = 0.348 Порог = 0.46 | Точность = 0.522, Полнота = 0.259, F1 мера = 0.346 Порог = 0.48 | Точность = 0.506, Полнота = 0.221, F1 мера = 0.308
'Лучший результат'
'Порог = 0.22 | F1 - мера = 0.46078'
Построим транспонированнаю PR кривую (для сравнения с ROC-кривой).
model = LogisticRegression(random_state=rs)
model.fit(features_train_upsampled, target_train_upsampled)
my_pr_curve(model, features_valid_scaler, target_valid)
Построим ROC- кривую.
my_roc_curve(model, features_valid_scaler, target_valid)
Выводы:
Попробуем улучшить модель Логистической регрессии. Для усранения дисбаланса используем class_weight ='balanced'. Так как он показал хороший результат и возможность нормального расчета AUC-ROC.
# составим списки значений для параметров, которые мы хотим перебрать для поиска наилучшей модели
solver_list = ['lbfgs', 'liblinear', 'newton-cg', 'newton-cholesky', 'sag', 'saga']
c_list = [1, 0.1, 0.2, 0.3 , 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
penalty_list = ['l2', 'l1', 'elasticnet', None]
l1_ratio_list = [None, 0.1, 0.2, 0.3 , 0.4, 0.5, 0.6, 0.7, 0.8, 0.9] # Только, если penalty = 'elasticnet'
best_result=0
# циклами по этим параметрам дойдем до
for solver_value in solver_list:
for c_value in c_list:
for penalty_value in penalty_list:
if penalty_value == 'elasticnet':
for l1_ratio_value in l1_ratio_list:
try: # схалявим и вместо изучения всех зависимостей параметров воспользуемся try except
model = LogisticRegression( random_state=rs
, class_weight='balanced'# создание модели
, solver=solver_value
, C=c_value
, penalty=penalty_value
,l1_ratio=l1_ratio_value
)
model.fit(features_train_scaler, target_train) # обучения модели и проверки ее характеристик
predicted_valid = model.predict(features_valid_scaler)
result = f1_score(target_valid, predicted_valid) # посчитайте качество модели на валидационной выборке
except:
result = 0
if result > best_result:
best_model_logistic_regression = model # сохраните наилучшую модель
best_result = result # сохраните наилучшее значение метрики accuracy на валидационных данных
best_solver_value = solver_value
best_c_value = c_value
best_penalty_value = penalty_value
best_l1_ratio_value = l1_ratio_value
else:
try:
model = LogisticRegression( random_state=rs
, class_weight='balanced'
, solver=solver_value
, C=c_value
, penalty=penalty_value
)
model.fit(features_train_scaler, target_train)
predicted_valid = model.predict(features_valid_scaler)
result = f1_score(target_valid, predicted_valid) # посчитайте качество модели на валидационной выборке
except:
result = 0
if result > best_result:
best_model_logistic_regression = model # сохраните наилучшую модель
best_result = result # сохраните наилучшее значение метрики accuracy на валидационных данных
best_solver_value = solver_value
best_c_value = c_value
best_penalty_value = penalty_value
# выведем наши результаты
display("F1-мера наилучшей модели на проверочной выборке:", best_result)
display('Лучшие параметры:')
display('Solver=', best_solver_value)
display('C=', best_c_value)
display('Penalty=', best_penalty_value)
try:
display('l1_ratio=', best_l1_ratio_value)
except:
display('l1_ratio= None')
'F1-мера наилучшей модели на проверочной выборке:'
0.45966228893058164
'Лучшие параметры:'
'Solver='
'liblinear'
'C='
0.1
'Penalty='
'l1'
'l1_ratio= None'
Посмотрим на результат AUC-ROC.
auc_roc(best_model_logistic_regression, features_valid_scaler, target_valid)
0.734342081488422
Выводы:
solver='saga, а не liblinear как ожидалось.Проверим как работает дерево решений на маштабированных признаках с разными методами устранения дисбаланса классов.
model = DecisionTreeClassifier(random_state=rs
, class_weight='balanced'
)
model.fit(features_train_scaler, target_train)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.46175 | AUC-ROC = 0.66142'
model = DecisionTreeClassifier(random_state=rs)
model.fit(features_train_upsampled, target_train_upsampled)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.47581 | AUC-ROC = 0.67082'
model = DecisionTreeClassifier(random_state=rs)
model.fit(features_train_downsampled, target_train_downsampled)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.47249 | AUC-ROC = 0.68561'
Выводы:
Поробуем улучшить модели Дерева решений на за счет изменения глубины. Мы будем работать на маштабированных данных и сбалансировнных классах. Рассмотрим все 3 метода.
# создадим датафрейм для записи наших наблюдений
stat_decision_tree = pd.DataFrame(columns=['depth', 'f1', 'auc_roc'])
for depth in range(1, 11):
model = DecisionTreeClassifier( random_state=rs
, max_depth=depth
# , class_weight='balanced'
) # задаем параметры модели
# обучаем модель
model.fit(features_train_upsampled, target_train_upsampled)
# model.fit(features_train_downsampled, target_train_downsampled)
# model.fit(features_train, target_train)
# записываем данные в датафрейм
stat_decision_tree = stat_decision_tree.append({'depth': depth
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
# выводим собранные данные
stat_decision_tree.sort_values(by='f1', ascending=False)
| depth | f1 | auc_roc | |
|---|---|---|---|
| 4 | 5.0 | 0.535637 | 0.794135 |
| 6 | 7.0 | 0.527331 | 0.784590 |
| 5 | 6.0 | 0.523017 | 0.788113 |
| 7 | 8.0 | 0.512432 | 0.771396 |
| 8 | 9.0 | 0.500000 | 0.755652 |
| 3 | 4.0 | 0.490672 | 0.771262 |
| 9 | 10.0 | 0.488121 | 0.726802 |
| 2 | 3.0 | 0.485839 | 0.762811 |
| 1 | 2.0 | 0.473054 | 0.715362 |
| 0 | 1.0 | 0.442623 | 0.656689 |
# создадим датафрейм для записи наших наблюдений
stat_decision_tree = pd.DataFrame(columns=['depth', 'f1', 'auc_roc'])
for depth in range(1, 11):
model = DecisionTreeClassifier( random_state=rs
, max_depth=depth
, class_weight='balanced'
) # задаем параметры модели
# обучаем модель
# model.fit(features_train_upsampled, target_train_upsampled)
# model.fit(features_train_downsampled, target_train_downsampled)
model.fit(features_train_scaler, target_train)
# записываем данные в датафрейм
stat_decision_tree = stat_decision_tree.append({'depth': depth
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
# выводим собранные данные
stat_decision_tree.sort_values(by='f1', ascending=False)
| depth | f1 | auc_roc | |
|---|---|---|---|
| 4 | 5.0 | 0.535637 | 0.794135 |
| 6 | 7.0 | 0.524168 | 0.779524 |
| 5 | 6.0 | 0.523017 | 0.788113 |
| 7 | 8.0 | 0.513543 | 0.771785 |
| 8 | 9.0 | 0.497863 | 0.753218 |
| 3 | 4.0 | 0.490672 | 0.771262 |
| 9 | 10.0 | 0.487541 | 0.726636 |
| 2 | 3.0 | 0.485839 | 0.762811 |
| 1 | 2.0 | 0.473054 | 0.715362 |
| 0 | 1.0 | 0.442623 | 0.656689 |
# создадим датафрейм для записи наших наблюдений
stat_decision_tree = pd.DataFrame(columns=['depth', 'f1', 'auc_roc'])
for depth in range(1, 11):
model = DecisionTreeClassifier(random_state=rs
, max_depth=depth
# , class_weight='balanced'
) # задаем параметры модели
# обучаем модель
# model.fit(features_train_upsampled, target_train_upsampled)
model.fit(features_train_downsampled, target_train_downsampled)
# model.fit(features_train, target_train)
# записываем данные в датафрейм
stat_decision_tree = stat_decision_tree.append({'depth': depth
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
# выводим собранные данные
stat_decision_tree.sort_values(by='f1', ascending=False)
| depth | f1 | auc_roc | |
|---|---|---|---|
| 6 | 7.0 | 0.544102 | 0.790225 |
| 5 | 6.0 | 0.539166 | 0.804736 |
| 4 | 5.0 | 0.534054 | 0.803388 |
| 3 | 4.0 | 0.528662 | 0.784473 |
| 7 | 8.0 | 0.528616 | 0.766363 |
| 8 | 9.0 | 0.512974 | 0.755984 |
| 9 | 10.0 | 0.495540 | 0.734423 |
| 1 | 2.0 | 0.483493 | 0.716623 |
| 2 | 3.0 | 0.483493 | 0.769027 |
| 0 | 1.0 | 0.447859 | 0.658830 |
Выводы:
Проверим как работают модели Случайного Леса на настройках по умолчанию на маштабированных данных при различных методах балансировки классов.
model = RandomForestClassifier(random_state=rs, class_weight='balanced')
model.fit(features_train_scaler, target_train)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.53565 | AUC-ROC = 0.82198'
model = RandomForestClassifier(random_state=rs)
# model.fit(features_train_downsampled, target_train_downsampled)
model.fit(features_train_downsampled, target_train_downsampled)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.55385 | AUC-ROC = 0.81775'
model = RandomForestClassifier(random_state=rs)
# model.fit(features_train_downsampled, target_train_downsampled)
model.fit(features_train_upsampled, target_train_upsampled)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(model, features_valid_scaler, target_valid),
auc_roc(model, features_valid_scaler, target_valid)))
'F1-мера = 0.57399 | AUC-ROC = 0.81779'
Выводы:
Проверим пределы до котороых мы можем поднять F1 для каждого из методов.
%%time
stat_random_forest = pd.DataFrame(columns=['n_estimators', 'depth', 'f1', 'auc_roc'])
for est in range(1, 21):
for depth in range(1, 21):
model = RandomForestClassifier(n_estimators=est, max_depth=depth, class_weight='balanced', random_state=rs) # обучите модель с заданным количеством деревьев
model.fit(features_train_scaler, target_train) # обучите модель на тренировочной выборке
# собираем данные
stat_random_forest = stat_random_forest.append({'depth': depth
, 'n_estimators': est
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
stat_random_forest.sort_values(by='f1', ascending=False).head(10)
Wall time: 35.2 s
| n_estimators | depth | f1 | auc_roc | |
|---|---|---|---|---|
| 267 | 14.0 | 8.0 | 0.563482 | 0.815784 |
| 206 | 11.0 | 7.0 | 0.560748 | 0.815211 |
| 287 | 15.0 | 8.0 | 0.560292 | 0.815104 |
| 307 | 16.0 | 8.0 | 0.558931 | 0.815035 |
| 347 | 18.0 | 8.0 | 0.558140 | 0.812727 |
| 327 | 17.0 | 8.0 | 0.557856 | 0.814618 |
| 252 | 13.0 | 13.0 | 0.555891 | 0.816991 |
| 248 | 13.0 | 9.0 | 0.555698 | 0.813145 |
| 226 | 12.0 | 7.0 | 0.554642 | 0.814732 |
| 151 | 8.0 | 12.0 | 0.554286 | 0.796849 |
%%time
stat_random_forest = pd.DataFrame(columns=['n_estimators', 'depth', 'f1', 'auc_roc'])
for est in range(1, 41):
for depth in range(1, 21):
model = RandomForestClassifier(n_estimators=est, max_depth=depth, random_state=rs) # обучите модель с заданным количеством деревьев
model.fit(features_train_downsampled, target_train_downsampled) # обучите модель на тренировочной выборке
# собираем данные
stat_random_forest = stat_random_forest.append({'depth': depth
, 'n_estimators': est
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
stat_random_forest.sort_values(by='f1', ascending=False).head(10)
Wall time: 1min 24s
| n_estimators | depth | f1 | auc_roc | |
|---|---|---|---|---|
| 358 | 18.0 | 19.0 | 0.559917 | 0.806902 |
| 748 | 38.0 | 9.0 | 0.558824 | 0.816808 |
| 728 | 37.0 | 9.0 | 0.558577 | 0.816503 |
| 788 | 40.0 | 9.0 | 0.558577 | 0.816952 |
| 768 | 39.0 | 9.0 | 0.557308 | 0.816715 |
| 729 | 37.0 | 10.0 | 0.557173 | 0.815482 |
| 169 | 9.0 | 10.0 | 0.554639 | 0.805756 |
| 609 | 31.0 | 10.0 | 0.554622 | 0.813620 |
| 573 | 29.0 | 14.0 | 0.554315 | 0.809793 |
| 709 | 36.0 | 10.0 | 0.554292 | 0.815762 |
%%time
stat_random_forest = pd.DataFrame(columns=['n_estimators', 'depth', 'f1', 'auc_roc'])
for est in range(1, 31):
for depth in range(1, 31):
model = RandomForestClassifier(n_estimators=est, max_depth=depth, random_state=rs) # обучите модель с заданным количеством деревьев
model.fit(features_train_upsampled, target_train_upsampled) # обучите модель на тренировочной выборке
# собираем данные
stat_random_forest = stat_random_forest.append({'depth': depth
, 'n_estimators': est
, 'f1': f_one(model, features_valid_scaler, target_valid)
, 'auc_roc': auc_roc(model, features_valid_scaler, target_valid)
}
, ignore_index=True)
stat_random_forest.sort_values(by='f1', ascending=False).head(10)
Wall time: 2min 33s
| n_estimators | depth | f1 | auc_roc | |
|---|---|---|---|---|
| 569 | 19.0 | 30.0 | 0.598025 | 0.807880 |
| 568 | 19.0 | 29.0 | 0.598025 | 0.807880 |
| 567 | 19.0 | 28.0 | 0.598025 | 0.807880 |
| 566 | 19.0 | 27.0 | 0.598025 | 0.807880 |
| 565 | 19.0 | 26.0 | 0.598025 | 0.807880 |
| 564 | 19.0 | 25.0 | 0.592384 | 0.807215 |
| 503 | 17.0 | 24.0 | 0.590071 | 0.807007 |
| 563 | 19.0 | 24.0 | 0.590071 | 0.807904 |
| 504 | 17.0 | 25.0 | 0.588402 | 0.805990 |
| 440 | 15.0 | 21.0 | 0.587079 | 0.808900 |
Выводы:
Запишем модель с максимальной метрикой F1, а у нас 5 таких моделей с равным количеством деревьев и метрикой AUC-ROC, и минимальной глубиной этих деревьев. И назовем эту модель чемпионом. А только чемпионы достойны проверить себя на тестовой выборке.
# создадим модель чемпиона
best_random_forest_model = RandomForestClassifier(random_state=rs
, n_estimators=19
, max_depth=26
# , class_weight='balanced'
)
#обучим чемпиона на маштабированных признаках при сбалансированном целевом признаке
best_random_forest_model.fit(features_train_upsampled, target_train_upsampled)
# best_random_forest_model.fit(features_train_downsampled, target_train_downsampled)
# best_random_forest_model.fit(features_train_scaler, target_train)
RandomForestClassifier(max_depth=26, n_estimators=19, random_state=54321)
display ('F1-мера = {:.5f} | AUC-ROC = {:.5f}'.format(f_one(best_random_forest_model, features_test_scaler, target_test),
auc_roc(best_random_forest_model, features_test_scaler, target_test)))
'F1-мера = 0.59944 | AUC-ROC = 0.83485'
Вывод: Ура! Мы получили результат соответствующий целям проекта: модель показала F1 меру больше 0,59 на проверочной и тестовой выборках. При этом метрика AUC-ROC показал, что модель точно не является случайной. Это не самое высокое значение AUC-ROC, но одно из самых высоких. Модель лучше чем случайная, а значит адекватная.
Нам удалось достич поставленных целей, а именно получить модель, которая может предсказывать уйдет клиент из "Бета-Банка" на основе уже имеющихся данных, с точностью выше 0,59 по F1 мере.
Полученная модель - это модель Случайного Леса с 19 деревьями и глубиной/высотой деревьев 26. Эта модель работает на маштабированных признаках (среднее каждого признака 0, а дисперсия 1), и сбалансированном целевом признаке по методу upsampling. AUC-ROC этой модели больше 0,8, что говорит о том что эта модель работает значительно лучше случайной (является адекватной).